1 /* FOREGEJ - FOrmatting REfactoring GEnerating Java
2 *
3 * Copyright (C) 2003 Andreas Arrgard
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 package com.octagroup.foregej.antlr;
20 import java.util.HashSet;
21 import java.util.Vector;
22 import com.octagroup.foregej.Settings;
23 import com.octagroup.foregej.java.lang.ast.AST_IF;
24 import com.octagroup.foregej.java.lang.ast.AST_SLIST;
25 import antlr.Token;
26 import antlr.collections.AST;
27 /***
28 * Class used to write output from nodes.
29 */
30 public class NodeWriter
31 {
32 /***
33 * Integer used to signal that we cannot wrap on a char or string.
34 */
35 public static final int WRAP_NOT=0;
36 /***
37 * Integer used to signal that we can wrap before a char or string.
38 */
39 public static final int WRAP_BEFORE=1;
40 /***
41 * Integer used to signal that we can wrap after a char or string.
42 */
43 public static final int WRAP_AFTER=2;
44 /***
45 * Integer used to signal that we can wrap on a char or string.
46 */
47 public static final int WRAP_ON=3;
48 /***
49 * This string buffer contains th written output
50 */
51 private StringBuffer sb_=new StringBuffer();
52 /***
53 * A vector that contains a set of indentations
54 */
55 private Vector indentations_=new Vector();
56 /***
57 * the parent node
58 */
59 protected Vector parents_=new Vector();
60 /***
61 * a hashmap of all the written tokens before and after a node.
62 */
63 protected HashSet writtenTokens_=new HashSet();
64 /***
65 * A reference to the indentation at the latest wrap prospect.
66 */
67 protected String latestWrapProspectIndent_=null;
68 /***
69 * Signals how the wrapping choulb be performed. ie should the
70 * character be removed, or should wrapping occurr before or after.
71 */
72 protected int latestWrapProspectType_=0;
73 /***
74 * The position of the latest line wrap prospect. If this is the same
75 * as the lastLineWrap we have not discovered any characters that we
76 * can wrap at.
77 */
78 protected int latestWrapProspectPos_=0;
79 /***
80 * The position of the last line wrap.
81 */
82 protected int lastLineWrapPos_=0;
83 /***
84 * How do we deal with lines that exceed the maximum number of
85 * characters.
86 */
87 protected boolean canWrapLines_=true;
88 /***
89 * This variable is assigned as we exit the write(AST) method and used
90 * to check if we can wrap lines between two asts as we enter it again
91 * with a new ast.
92 */
93 protected AST lastAST_=null;
94 /***
95 * Returns the output of the underlying buffer
96 *
97 * @return The text written to the underlying buffer.
98 */
99 public String toString()
100 {
101 return sb_.toString();
102 }
103 /***
104 * Copies the state from one node writer to another.
105 * <p>
106 * This method is used when we need to change the implementation of
107 * the node writer. For example when we switch between writing java
108 * code and java comments.
109 * </p>
110 *
111 * @param nw
112 */
113 public void copyThisStateTo(NodeWriter nw)
114 {
115 nw.sb_=sb_;
116 nw.indentations_=indentations_;
117 nw.lastLineWrapPos_=lastLineWrapPos_;
118 nw.latestWrapProspectPos_=latestWrapProspectPos_;
119 nw.latestWrapProspectType_=latestWrapProspectType_;
120 nw.latestWrapProspectIndent_=latestWrapProspectIndent_;
121 //
122 // ignore the parents until we find an "acceptable" way
123 // of initializing parent nodes...
124 //
125 //nw.parents_ = parents_;
126 }
127 /***
128 * Writes the supplied ast to the underlying stream.
129 *
130 * @param ast the ast to write.
131 * @throws RuntimeException if ast is null
132 */
133 public void write(AST ast)
134 {
135 if(ast==null) {
136 throw new RuntimeException("ast is null!");
137 }
138 BaseAST bast=(BaseAST)ast;
139 //
140 // store how we deal with line wrapping
141 //
142 boolean canWrapLines=canWrapLines_;
143 if(bast.getWrappingPreference()!=BaseAST.WRAP_PREF_NONE) {
144 canWrapLines_=bast.getWrappingPreference()==BaseAST.WRAP_PREF_OK;
145 }
146 //
147 // check if we can wrap between the last ast and this one
148 //
149 if(canWrapBetween(lastAST_, ast)) {
150 latestWrapProspectIndent_=getIndentation();
151 latestWrapProspectPos_=sb_.length();
152 latestWrapProspectType_=WRAP_BEFORE;
153 }
154 // the parent ast stack should point to the supplied ast.
155 // this way we can reach the parent by referencing one level up.
156 truncateParentStackTo(ast);
157 pushParent(ast);
158 (// System.out.println("Writing ast:" + ast.getClass().getName());
159 (BaseAST)ast).write(this);
160 // restore to how we dealt with wrapping before this AST was written
161 canWrapLines_=canWrapLines;
162 // this is the last ast written...
163 lastAST_=ast;
164 }
165 /***
166 * Writes data to the underlying buffer.<br>
167 * It checks if the token has been written before and does not write
168 * it if that is the case.
169 *
170 * @param token the token to get the text from.
171 */
172 public void write(Token token)
173 {
174 write(token, true);
175 }
176 /***
177 * Writes data to the underlying buffer.<br>
178 *
179 * @param token the token to get the text from.
180 * @param checkWritten
181 */
182 public void write(Token token,boolean checkWritten)
183 {
184 if(checkWritten&&isWritten(token)) {
185 return;
186 }
187 write(token.getText());
188 markAsWritten(token);
189 }
190 /***
191 * Writes data to the underlying buffer.
192 *
193 * @param s the string to output
194 */
195 public void write(String s)
196 {
197 char[] carr=s.toCharArray();
198 for(int i=0; i<s.length(); i++){
199 char c=carr[i];
200 switch(c) {
201 case '\r':
202
203 // if the next character is a newline we skip the newline.
204 if(carr.length>i+1&&carr[i+1]=='\n') i++;
205 case '\n':
206
207 newLine();
208 break;
209 default:
210
211 sb_.append(c);
212 }
213 //
214 // check if the current line has exceeded the maximum
215 // number of columns. If that is the case we wrap at the latest
216 // character that allowed it...
217 //
218 if(sb_.length()-lastLineWrapPos_>=Settings.getLineLength()) {
219 wrap();
220 }
221 //
222 // check if it is possible to wrap in this char. If that
223 // is the case store the position and whiw the wrapping
224 // should be done.
225 //
226 if(canWrapLines_&&canWrapOn(c)!=WRAP_NOT) {
227 latestWrapProspectType_=canWrapOn(c);
228 latestWrapProspectPos_=sb_.length()-1;
229 latestWrapProspectIndent_=getIndentation();
230 }
231 }
232 }
233 /***
234 * Returns how we can wrap on the supplied character.
235 *
236 * @param c the char to switch on
237 * @return an integer specifying which wrap algorithm to use.
238 */
239 protected int canWrapOn(char c)
240 {
241 if(c==' ') {
242 return WRAP_ON;
243 }
244 return WRAP_NOT;
245 }
246 /***
247 * Returns how we can wrap between the supplied asts.
248 *
249 * @param firstAst the ast to switch on
250 * @param secondAst the ast to switch on
251 * @return true if we can wrap between the asts.
252 */
253 protected boolean canWrapBetween(AST firstAst,AST secondAst)
254 {
255 return false;
256 }
257 /***
258 * Wraps a line at the lastLineWrapPos_ index.
259 */
260 private void wrap()
261 {
262 if(latestWrapProspectPos_==lastLineWrapPos_) {
263 return;
264 }
265 String lineEnding=Settings.getLineEnding();
266 if(latestWrapProspectType_==WRAP_ON) {
267 removeSpacesAfter(latestWrapProspectPos_);
268 sb_.insert(latestWrapProspectPos_,
269 lineEnding+latestWrapProspectIndent_);
270 lastLineWrapPos_=latestWrapProspectPos_+lineEnding.length();
271 } else {
272 int wrapPos=(latestWrapProspectType_==WRAP_BEFORE)?latestWrapProspectPos_:latestWrapProspectPos_+1;
273 removeSpacesAfter(wrapPos);
274 sb_.insert(wrapPos, lineEnding+latestWrapProspectIndent_);
275 lastLineWrapPos_=wrapPos+lineEnding.length();
276 }
277 latestWrapProspectPos_=lastLineWrapPos_;
278 }
279 /***
280 * Helper method that removes all the spaces after the supplied
281 * position.
282 * <p>
283 * This method is used when we wrap the lines to make sure that the
284 * indentation becomes correct.
285 * </p>
286 *
287 * @param startPos
288 */
289 public void removeSpacesAfter(int startPos)
290 {
291 int endPos=startPos;
292 while(sb_.length()>endPos&&sb_.charAt(endPos)==' ')endPos++;
293 sb_.replace(startPos, endPos, "");
294 }
295 /***
296 * Writes a new line sequence and then an indentation to the
297 * underlying buffer.
298 */
299 public void newLine()
300 {
301 //
302 // create line ending
303 //
304 sb_.append(Settings.getLineEnding());
305 lastLineWrapPos_=sb_.length();
306 latestWrapProspectPos_=lastLineWrapPos_;
307 //
308 // and add indentation
309 //
310 sb_.append(getIndentation());
311 }
312 /***
313 * Indents with the number of spaces defined in the settings.
314 * <p></p>
315 */
316 public void pushIndentation()
317 {
318 pushIndentation(Settings.getIndentation());
319 }
320 /***
321 * Indents with the supplied string.
322 * <p></p>
323 *
324 * @param indent
325 */
326 public void pushIndentation(String indent)
327 {
328 StringBuffer sb=new StringBuffer(getIndentation());
329 sb.append(indent);
330 indentations_.add(sb.toString());
331 }
332 /***
333 * Indents with the supplied number of spaces.
334 * <p></p>
335 *
336 * @param indent
337 */
338 public void pushIndentation(int indent)
339 {
340 StringBuffer sb=new StringBuffer(getIndentation());
341 for(int i=0; i<indent; i++){
342 sb.append(' ');
343 }
344 indentations_.add(sb.toString());
345 }
346 /***
347 * Pushes an indentaion string with the same number of spaces as the
348 * the number of characters on the last line.
349 */
350 public void pushIndentationCurrentPos()
351 {
352 StringBuffer indentation=new StringBuffer();
353 for(int i=sb_.length()-lastLineWrapPos_; i>0; i--){
354 indentation.append(' ');
355 }
356 indentations_.add(indentation.toString());
357 }
358 /***
359 * Helper method that scans the undelying buffer and returns the
360 * number of characters in the last line.
361 *
362 * @return the number of characters in the last line.
363 */
364 private Integer getLinePos()
365 {
366 for(int i=sb_.length()-1; i>=0; i--){
367 char c=sb_.charAt(i);
368 switch(c) {
369 case '\n':
370 case '\r':
371
372 return new Integer(sb_.length()-i);
373 default:
374
375 }
376 }
377 // continue
378 return new Integer(0);
379 }
380 /***
381 * This is the same as writing <code>popIndentation()</code>and then
382 * <code>write(s)</code>.<br>
383 * It exists because the <code>JavaNodeWriter</code>should be able to
384 * write comments before popping the indentation.
385 *
386 * @param s the string to write
387 * @return the last indentation.
388 */
389 public String popIndentationAndWrite(String s)
390 {
391 String lastIndent=popIndentation();
392 write(s);
393 return lastIndent;
394 }
395 /***
396 * Pops an indentation level off the stack.
397 *
398 * @return the indentation level that was removed.
399 */
400 public String popIndentation()
401 {
402 if(indentations_.size()==0) throw new IllegalStateException("NodeWriter: called popIndentaion with no indentations on the stack");
403 String oldIndentation=(String)indentations_.remove(indentations_.size()-1);
404 String currIndentation=getIndentation();
405 boolean indentationOnly=sb_.toString().endsWith(oldIndentation);
406 if(indentationOnly) {
407 if(oldIndentation.startsWith(currIndentation)) {
408 int remove=oldIndentation.length()-currIndentation.length();
409 sb_.setLength(sb_.length()-remove);
410 } else if(currIndentation.startsWith(oldIndentation)) {
411 String add=currIndentation.substring(oldIndentation.length(),
412 currIndentation.length());
413 sb_.append(add);
414 } else {
415 throw new IllegalStateException("Indentations messed up!");
416 }
417 }
418 return oldIndentation;
419 }
420 /***
421 * Returns the indentation level.
422 *
423 * @return the indentation level.
424 */
425 public String getIndentation()
426 {
427 if(indentations_.size()==0) return "";
428 return (String)indentations_.get(indentations_.size()-1);
429 }
430 /***
431 * Sets the parent node.
432 *
433 * @param parent
434 */
435 private void pushParent(AST parent)
436 {
437 parents_.add(parent);
438 }
439 /***
440 * Helper method that determines if the supplied node is a child or
441 * sibling node to the supplied parent.
442 *
443 * @param parent
444 * @param child
445 * @return
446 */
447 private boolean isChild(AST parent,AST child)
448 {
449 AST node=parent.getFirstChild();
450 while(node!=null){
451 if(child==node) {
452 return true;
453 }
454 node=node.getNextSibling();
455 }
456 return false;
457 }
458 /***
459 * Helper method that trunkates the parent stack to the position where
460 * the supplied node is a child member.
461 * <p>
462 * If the parents sttck is empty this method dont do nothing.
463 * </p>
464 *
465 * @param child
466 */
467 private void truncateParentStackTo(AST child)
468 {
469 // if the parents stack is empty we just return.
470 // this is a special case
471 if(parents_.size()==0) {
472 return;
473 }
474 int i=parents_.size()-1;
475 for(; i>=0; i--){
476 if(isChild((AST)parents_.get(i), child)) {
477 break;
478 }
479 }
480 // if node is not child of any current parents
481 // then we have not written the parent using the
482 // node writer - at this point this is illegal.
483 if(i==-1) throw new IllegalStateException("Failed to find parent of node.");
484 parents_.setSize(i+1);
485 }
486 /***
487 * @return
488 */
489 public AST getPreviousAST()
490 {
491 return getPreviousAST((AST)parents_.get(parents_.size()-1));
492 }
493 /***
494 * @param ast
495 * @return
496 */
497 public AST getPreviousAST(AST ast)
498 {
499 AST parent=((BaseAST)ast).getParentAst();
500 if(parent==null) {
501 //throw new IllegalStateException("Failed to find parent to ast.");
502 return null;
503 }
504 AST prevNode=null;
505 AST node=parent.getFirstChild();
506 while(node!=null){
507 if(node==ast) {
508 return prevNode;
509 }
510 prevNode=node;
511 node=node.getNextSibling();
512 }
513 throw new IllegalStateException("Failed to find node in parent");
514 }
515 /***
516 * Marks the supplied token as written.
517 *
518 * @param token the token to mark.
519 */
520 public void markAsWritten(Token token)
521 {
522 writtenTokens_.add(token);
523 }
524 /***
525 * Returns true if the supplied token is written.
526 *
527 * @param token the token to determine if written.
528 * @return true if the supplied token is written.
529 */
530 public boolean isWritten(Token token)
531 {
532 return writtenTokens_.contains(token);
533 }
534 /***
535 * Helper method that writes the supplied ast as a statement.
536 * <p>
537 * This method is invoked from the <code>if</code>and
538 * <code>while</code>asts.
539 * </p>
540 *
541 * @param ast the ast to write
542 */
543 public void writeStatement(AST ast)
544 {
545 if(ast instanceof AST_SLIST||ast instanceof AST_IF) {
546 write(ast);
547 } else {
548 write(ast);
549 write(";");
550 }
551 }
552 /***
553 * Helper method that writes the supplied ast in parenthesis.
554 * <p>
555 * This method is invoked from the asts that may need this
556 * functionality to separate two operands with same precidence.
557 * </p>
558 *
559 * @param op the operator to write in the parenthesis
560 */
561 public void writeInParenthesis(AST op)
562 {
563 write("(");
564 pushIndentationCurrentPos();
565 write(op);
566 popIndentation();
567 write(")");
568 }
569 /***
570 * Returns true if the last line is empty.
571 * <p>
572 * This method returns true if the internal string buffer ends with
573 * the new line fetched frm the settings and the current indentation.
574 * </p>
575 *
576 * @return
577 */
578 public boolean isLastLineEmpty()
579 {
580 String comp=Settings.getLineEnding()+getIndentation();
581 String ending=sb_.substring(sb_.length()-comp.length(),
582 sb_.length());
583 return comp.equals(ending);
584 }
585 /***
586 * Sets the flag that indicates if we are allowed to wrap lines.
587 *
588 * @param b should wrapping be enabled
589 * @return the old status.
590 */
591 public boolean enableWrapping(boolean b)
592 {
593 boolean retVal=canWrapLines_;
594 canWrapLines_=b;
595 return retVal;
596 }
597 }
This page was automatically generated by Maven